﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace SharedGenomics.Core.Collections
{
    public class SignalledQueue<T>
    {
        private readonly Action<T> _itemAction;
        private Queue<T> _queue;
        private object _lock = new object();
        private ManualResetEvent _queueWaitHandle;
        private IList<Thread> _threads;
        private bool _isRunning;

        public SignalledQueue(Action<T> itemAvailableAction, int threadCount)
        {
            itemAvailableAction.ThrowIfNull("itemAvailableAction");
            this._itemAction = itemAvailableAction;

            this._queue = new Queue<T>();
            this._queueWaitHandle = new ManualResetEvent(false);

            int count = Math.Min(1, threadCount);

            //start background queue monitor threads
            this._isRunning = true;
            this._threads = count.Of(i => new Thread(this.MonitorQueue) { IsBackground = true, Name = String.Format("RequestQueueThread{0}", i) }).ToList();
            this._threads.ForEach(t => t.Start());
        }

        public void Enqueue(T item)
        {
            lock (this._lock)
            {
                this._queue.Enqueue(item);
                this._queueWaitHandle.Set();
            }

            //this.StartMonitorsIfRequired();
        }

        public void Stop()
        {
            //unset isRunning flag and wait for all threads to end on another thread

            //once all threads have finished check no new items are in the queu
            throw new NotImplementedException();
        }

        private void StartMonitorsIfRequired()
        {
            throw new NotImplementedException();
        }

        private void MonitorQueue()
        {
            T dequeuedItem;

            while (this._isRunning)
            {
                if (this._queueWaitHandle.WaitOne(1000) && this.TryDequeueItem(out dequeuedItem))
                {
                    try
                    {
                        //execute request for dequeued item
                        this._itemAction(dequeuedItem);
                    }
                    catch (Exception)
                    {
                        //swallow any exceptions otherwise this thread will be taken down
                        //TODO: allow client to specify an error callback?
                    }
                }
            }
        }

        private bool TryDequeueItem(out T item)
        {
            item = default(T);
            bool itemFound = false;

            lock (this._lock)
            {
                if (this._queue.Count > 0)
                {
                    item = this._queue.Dequeue();
                    itemFound = true;

                    if (this._queue.Count == 0)
                    {
                        this._queueWaitHandle.Reset();
                    }
                }
            }

            return itemFound;
        }
    }
}
